package org.xiaoxin.onecoupon.merchant.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.awaitility.spi.Timeout;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xiaoxin.onecoupon.framework.exception.ClientException;
import org.xiaoxin.onecoupon.merchant.admin.common.context.UserContext;
import org.xiaoxin.onecoupon.merchant.admin.common.enums.CouponTaskSendTypeEnum;
import org.xiaoxin.onecoupon.merchant.admin.common.enums.CouponTaskStatusEnum;
import org.xiaoxin.onecoupon.merchant.admin.common.enums.CouponTemplateStatusEnum;
import org.xiaoxin.onecoupon.merchant.admin.dao.entity.CouponTaskDO;
import org.xiaoxin.onecoupon.merchant.admin.dao.mapper.CouponTaskMapper;
import org.xiaoxin.onecoupon.merchant.admin.dto.req.CouponTaskCreateReqDTO;
import org.xiaoxin.onecoupon.merchant.admin.dto.resp.CouponTemplateQueryRespDTO;
import org.xiaoxin.onecoupon.merchant.admin.mq.event.CouponTaskExecuteEvent;
import org.xiaoxin.onecoupon.merchant.admin.mq.producer.CouponTaskActualExecuteProducer;
import org.xiaoxin.onecoupon.merchant.admin.service.CouponTaskService;
import org.xiaoxin.onecoupon.merchant.admin.service.CouponTemplateService;
import org.xiaoxin.onecoupon.merchant.admin.service.handler.excel.RowCountListener;

import java.util.Objects;
import java.util.concurrent.*;

/**
 * @author xiaoxin
 * 2025/4/18
 */
@Service
@RequiredArgsConstructor
public class CouponTaskServiceImpl extends ServiceImpl<CouponTaskMapper, CouponTaskDO> implements CouponTaskService {
    private  final  CouponTemplateService couponTemplateService;
    private  final  CouponTaskMapper couponTaskMapper;

    private final RedissonClient redissonClient;
    private final CouponTaskActualExecuteProducer couponTaskActualExecuteProducer;

    /**
     * 创建线程池
     */
    private  final ExecutorService executorService =new ThreadPoolExecutor(
            //核心线程数量
            Runtime.getRuntime().availableProcessors(),
            //最大线程数量
            Runtime.getRuntime().availableProcessors() << 1,
            //线程池中多余的线程（超过核心线程数的线程）的存活时间，单位由 TimeUnit 指定。
            60,
            TimeUnit.SECONDS,
            //使用了 SynchronousQueue 作为阻塞队列
            new SynchronousQueue<>(),
            //使用了 DiscardPolicy 作为拒绝策略。当线程池已满（达到最大线程数且队列已满）时，新提交的任务会被丢弃，不会抛出异常。
            new ThreadPoolExecutor.DiscardPolicy()
    );
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createCouponTask(CouponTaskCreateReqDTO requestParam) {
        // 验证非空参数
        // 验证参数是否正确，比如文件地址是否为我们期望的格式等
        // 验证参数依赖关系，比如选择定时发送，发送时间是否不为空等
        CouponTemplateQueryRespDTO couponTemplate = couponTemplateService.findCouponTemplateById(requestParam.getCouponTemplateId());
        if (couponTemplate == null){
            throw new ClientException("优惠券模板不存在，请检查提交信息是否正确");
        }
        if (ObjectUtil.equals(couponTemplate.getStatus(), CouponTemplateStatusEnum.ENDED.getStatus())){
            throw new ClientException("优惠券模板已过期");
        }
        if(ObjectUtil.equals(requestParam.getSendType(), CouponTaskSendTypeEnum.SCHEDULED.getType())&& requestParam.getSendTime() ==null){
            throw new ClientException("分发任务类型为定时任务但时间为空");
        }
        //构建优惠券推送任务数据库持久层实体
        CouponTaskDO couponTaskDO = BeanUtil.copyProperties(requestParam, CouponTaskDO.class);
        couponTaskDO.setBatchId(IdUtil.getSnowflakeNextId());
        couponTaskDO.setOperatorId(Long.parseLong(UserContext.getUserId()));
        couponTaskDO.setShopNumber(UserContext.getShopNumber());
        couponTaskDO.setStatus(
                Objects.equals(requestParam.getSendType(), CouponTaskSendTypeEnum.IMMEDIATE.getType())
                        ? CouponTaskStatusEnum.IN_PROGRESS.getStatus()
                        : CouponTaskStatusEnum.PENDING.getStatus()
        );
        // 保存优惠券推送任务记录到数据库
        couponTaskMapper.insert(couponTaskDO);
        /**
        //通过EasyEcel监听器获取Excel表格所有行数
        RowCountListener rowCountListener = new RowCountListener();
        EasyExcel.read(requestParam.getFileAddress(),rowCountListener).sheet().doRead();
        int rowCount = rowCountListener.getRowCount();
        couponTaskDO.setSendNum(rowCount);
         **/
        //
        // 为什么需要统计行数？因为发送后需要比对所有优惠券是否都已发放到用户账号
        // 100 万数据大概需要 4 秒才能返回前端，如果加上验证将会时间更长，所以这里将最耗时的统计操作异步化
        JSONObject delayJsonObject = JSONObject.of("fileAddress", requestParam.getFileAddress(), "couponTaskId", couponTaskDO.getId());
        executorService.execute(()->refreshCouponTaskSendNum(delayJsonObject));

        // 假设刚把消息提交到线程池，突然应用宕机了，我们通过延迟队列进行兜底 Refresh
        RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque("COUPON_TASK_SEND_NUM_DELAY_QUEUE");
        RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        // 这里延迟时间设置 20 秒，原因是我们笃定上面线程池 20 秒之内就能结束任务
        delayedQueue.offer(delayJsonObject, 20, TimeUnit.SECONDS);

        //如果是立即发现任务,直接调用消息队列进行发送流程
        if(Objects.equals(requestParam.getSendType(),CouponTaskSendTypeEnum.IMMEDIATE.getType())){
            CouponTaskExecuteEvent couponTaskExecuteEvent = CouponTaskExecuteEvent.builder().couponTaskId(couponTaskDO.getId()).build();
            couponTaskActualExecuteProducer.sendMessage(couponTaskExecuteEvent);
        }

    }
    public   void refreshCouponTaskSendNum(JSONObject delayJsonObject){
        RowCountListener rowCountListener = new RowCountListener();
        EasyExcel.read(delayJsonObject.getString("fileAddress"),rowCountListener).sheet().doRead();
        int rowCount=rowCountListener.getRowCount();
        CouponTaskDO updateCouponTaskDO = CouponTaskDO.builder()
                .id(delayJsonObject.getLong("couponTaskId"))
                .sendNum(rowCount)
                .build();
        couponTaskMapper.updateById(updateCouponTaskDO);

    }




}
